/*
 * StyleSheet.java  0.4.0 / Oct 26, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;

import com.cute4j.util.Utilities;

/**
 * Style Sheet.
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class StyleSheet {

// Private Fields. -------------------------------------------------------------

  private String text;
  private boolean valid;
  private boolean converted;
  private WeakReference textCache;

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new StyleSheet object.
   *
   * @param styleSheetStream the style sheet input stream
   *
   * @throws IOException on an I/O error
   */
  public StyleSheet(InputStream styleSheetStream) throws IOException {
    this(Utilities.toString(styleSheetStream));
  }

  /**
   * Constructs new StyleSheet object.
   *
   * @param styleSheetFile the style sheet file
   *
   * @throws IOException on an I/O error
   */
  public StyleSheet(File styleSheetFile) throws IOException {
    this(Utilities.toString(new FileReader(styleSheetFile)));
  }

  /**
   * Constructs new StyleSheet object.
   *
   * @param styleSheetText the style sheet text
   */
  public StyleSheet(String styleSheetText) {
    if (styleSheetText == null) {
      throw new IllegalArgumentException("Null style sheet");
    }
    text = styleSheetText;
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Returns <tt>true</tt> if the style sheet is valid.
   *
   * @return <tt>true</tt> if the style sheet is valid
   */
  public final boolean isValid() {
    if (!converted) {
      makeCache();
    }
    return valid;
  }

  /**
   * Returns style sheet text.
   *
   * @return style sheet text
   */
  public final String getText() {
    return text;
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if ((obj != null) && (obj.getClass() == getClass())) {
      return text.equals(((StyleSheet)obj).text);
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  public int hashCode() {
    return text.hashCode();
  }

// Package-Private Methods. ----------------------------------------------------

  String getStyleSheetText() {
    if (!converted) {
      makeCache();
    }
    if (valid) {
      String cachedText = (String)textCache.get();
      if (cachedText == null) {
        cachedText = convertStyleSheet(text);
        textCache = new WeakReference(cachedText);
      }
      return cachedText;
    }
    return null;
  }

// Private Methods. ------------------------------------------------------------

  private void makeCache() {
    String cachedText = convertStyleSheet(text);
    if (cachedText == null) {
      valid = false;
    } else {
      textCache = new WeakReference(cachedText);
      valid = true;
    }
    converted = true;
  }

  private String convertStyleSheet(String styleSheetText) {
    // TODO: implement better converter
    // TODO: add tooltip support
    int textLen = styleSheetText.length();
    StringBuffer sb = new StringBuffer(textLen << 1);
    StringBuffer selector = new StringBuffer();
    int state = 0;
    boolean insideBlock = false;
    for (int i = 0; i < textLen; i++) {
      char c = styleSheetText.charAt(i);
      switch (state) { // NOPMD
        case 0: // selector state
          if (c == '/') {
            state = 101;
          } else if (c == '{') {
            sb.append(c);
            state = 201;
          } else if (Character.isJavaIdentifierStart(c)) {
            selector.setLength(0);
            selector.append("QS_");
            selector.append(c);
            state = 1;
          } else {
            sb.append(c);
          }
          break;
        case 1:
          if (Character.isJavaIdentifierPart(c)) {
            selector.append(c);
            break;
          } else if (c == '/') {
            state = 101;
          } else if (c == '{') {
            state = 201;
          } else if (c == ':') {
            state = 2;
          } else {
            state = 0;
          }
          sb.append("QWidget[");
          sb.append(selector);
          sb.append(']');
          sb.append(c);
          break;
        case 2:
          if ((c != ':') && !Character.isJavaIdentifierPart(c)) {
            state = 0;
          }
          sb.append(c);
          break;
        case 101: // comment state
          if (c != '*') {
            return null;
          }
          state = 102;
          break;
        case 102:
          if (c == '*') {
            state = 103;
          }
          break;
        case 103:
          if (c != '/') { // NOPMD
            state = 102;
          } else if (insideBlock) {
            state = 201;
          } else {
            state = 0;
          }
          break;
        case 201: // block state
          if (c == '/') {
            state = 101;
            break;
          }
          if (c == '(') {
            state = 202;
          } else if (c == '}') {
            state = 0;
          }
          sb.append(c);
          break;
        case 202: // url state
          if (c == ')') {
            state = 201;
          }
          sb.append(c);
          break;
      }
    }
    return sb.toString();
  }

}
